/* Sokoban
 * HIT3243
 * Authors: Mike Blackney, Ben Ingarfield, Kurniawan
 */

#include <fstream>
#include "hiscore.h"
#include "registrar.h"
#include "level.h"

#define IDC_EDITBOX 1

// Here are the GUI magic numbers:
#define TEXTBOX_Y 250
#define FIRST_SCORE_Y 300
#define NAME_X 150
#define PUSH_X 450
#define TURN_X 550
#define TIME_X 650
#define SCORE_INDEX_OFFSET 25
#define NUM_HISCORES 6
#define MAX_STR_LEN 255
#define LEVEL_POS D3DXVECTOR2(400,250)

// Time before user is taken back to main screen:
#define EXIT_TIMEOUT 6.0f // Six seconds

// The score file name:
#define SCORE_FILE L"sokoban_scores.txt"

HiScore::HiScore() {
	// The first time a HiScore is created, load
	// the high score table from the saved file.

	// Now load the score file:
	HRESULT hr;
	WCHAR strPath[MAX_PATH];
	hr = DXUTFindDXSDKMediaFileCch( strPath, MAX_PATH, SCORE_FILE );
	if(!FAILED(hr)) {
		// If the score file *is* found, load it:
		std::wifstream score_file(strPath);
		assert(score_file);

		std::wstring name;

		int turns, pushes;
		double time;

		int list = 0;
		while (!score_file.eof( )) {
			wchar_t storage[MAX_STR_LEN];
			// Read the first line: a name.
			score_file.getline( storage, MAX_STR_LEN );
			name = storage;
			// If there was no name, it means the start of a new list.
			if (name == L"") {
				++list;
				continue;
			}
			// Read the numbers on the next lines:
			score_file >> turns;
			score_file >> pushes;
			score_file >> time;
			// Read the trailing newline:
			score_file.getline( storage, MAX_STR_LEN );
			HiScoreLists[list].push_back( HiScoreRecord( name, turns, pushes, time ) );
		}

		score_file.close( );
	}
	
}

HiScore::~HiScore( ) {
// The destructor saves our high score list to file.
	WCHAR strPath[MAX_PATH];
	DXUTFindDXSDKMediaFileCch( strPath, MAX_PATH, SCORE_FILE );
	
	std::wofstream score_file(strPath);
	// Here we make sure that we could open the file for output.
	// If the disk is locked, we should gracefully exit...  I hope :)
	if (score_file) {

		// Go to the start of the file - we don't want to keep old scores:
		score_file.seekp(0);

		for (unsigned int list = 0; list < HiScoreLists.size( ); ++list) {
			std::list<HiScoreRecord>::iterator iter = HiScoreLists[list].begin( );
			int i;
			for (i = 0; (iter != HiScoreLists[list].end( )) && (i < NUM_HISCORES); ++i, ++iter)
				score_file << iter->sName << '\n' 
						<< iter->iTurns << '\n'
						<< iter->iPushes << '\n'
						<< iter->fTime << '\n';

			score_file << '\n';

		}

	score_file.close( );

	}
}

HiScore* const HiScore::Instance() {
	static HiScore instance;
	return &instance;
}

HRESULT HiScore::Load() {
	HRESULT hr;

	V_RETURN(GameStateGUI::Load());

	// Get the title sprite:
	const D3DSURFACE_DESC* d = DXUTGetBackBufferSurfaceDesc();
	V_RETURN(title.CreateFromFile(L"s_hiscore.jpg"));
	title.SetPosition( d->Width / 2 - title.GetWidth( ) / 2, d->Height / 4 - title.GetHeight( ) / 2 );

	// Load the font:
	V_RETURN(font.Load(L"Tahoma", 24, FW_NORMAL));

	// Add the GUI items
	// If there's a score for entry,
	bEnteringScore = Registrar::bEnteringScore;
	fExitTime = EXIT_TIMEOUT;
	
	if ( bEnteringScore ) {
		// and the high score is high enough to make the top list:
		std::list<HiScoreRecord>::iterator iter; 
		int i = 0;

		HiScoreRecord temp_score( 
			L"TEMP",
			Registrar::iNumTurns,
			Registrar::iNumPushes,
			Registrar::fTime );

		// Now assume it's a low score until we prove otherwise:
		bEnteringScore = false;
		for (iter = HiScoreLists[Registrar::iLastLevel].begin( ); 
			 iter != HiScoreLists[Registrar::iLastLevel].end( ) && i < NUM_HISCORES; 
			 ++iter)
			 if (temp_score < *iter)
				 bEnteringScore = true;

		// And if there are not many scores in the list, we have to be entering one:
		if ( HiScoreLists[Registrar::iLastLevel].size( ) < NUM_HISCORES )
			bEnteringScore = true;

		if (bEnteringScore) {
			// add the edit box for entering high scores:
			V_RETURN(dialog.AddEditBox(IDC_EDITBOX, NULL, d->Width / 2 - 100, TEXTBOX_Y, 200, 32, true));
			dialog.FocusDefaultControl( );
		}
	}

	// Enable mouse
	DXUTSetCursorSettings(true, false);
	DXUTGetD3DDevice()->ShowCursor(true);

	dxf::DXFSetClear(true);
	dxf::DXFSetClearColor(BLACK);
	return S_OK;
}

void HiScore::Unload() {
	// Reset the high score:
	//Registrar::SetScore(0);

	GameStateGUI::Unload();
	font.Unload( );
	title.Unload( );
}

void HiScore::Update(double fTime, float fElapsedTime) {
	// If we're not entering a score, we eventually exit:
	if (!bEnteringScore) {
		// Countdown:
		fExitTime -= fElapsedTime;

		// And then exit:
		if (fExitTime <= 0) {
			if (Registrar::bEnteringScore)
				dxf::DXFChangeState(Registrar::kGame);
			else 
				dxf::DXFChangeState(Registrar::kTitle);
		}
	}
}

void HiScore::Render2D(double fTime, float fElapsedTime) {
	// Render the "High Scores" sprite:
	title.Render2D( );

	// Render the level number:
	font.Render2D( L"LEVEL " + dxf::towstring(Registrar::iLastLevel + 1),
				   LEVEL_POS, WHITE, D3DXVECTOR2(1,1), true ); 

	// Render the scores:
	HiScoreLists[Registrar::iLastLevel].sort( );

	font.Render2D( L"Name",		D3DXVECTOR2(NAME_X,FIRST_SCORE_Y));
	font.Render2D( L"Pushes", 	D3DXVECTOR2(PUSH_X,FIRST_SCORE_Y));
	font.Render2D( L"Turns", 	D3DXVECTOR2(TURN_X,FIRST_SCORE_Y));
	font.Render2D( L"Time", 	D3DXVECTOR2(TIME_X,FIRST_SCORE_Y));

	std::list<HiScoreRecord>::iterator iter = HiScoreLists[Registrar::iLastLevel].begin( );
	int i;
	for (i = 1; (iter != HiScoreLists[Registrar::iLastLevel].end( )) && (i <= NUM_HISCORES); ++i, ++iter) {
		font.Render2D( (*iter).sName, 
			D3DXVECTOR2(NAME_X,FIRST_SCORE_Y + static_cast<float>(i)*SCORE_INDEX_OFFSET));
		font.Render2D( dxf::towstring((*iter).iPushes), 
			D3DXVECTOR2(PUSH_X,FIRST_SCORE_Y + static_cast<float>(i)*SCORE_INDEX_OFFSET));
		font.Render2D( dxf::towstring((*iter).iTurns), 
			D3DXVECTOR2(TURN_X,FIRST_SCORE_Y + static_cast<float>(i)*SCORE_INDEX_OFFSET));
		font.Render2D( dxf::towstring((static_cast<int>((*iter).fTime)) ), 
			D3DXVECTOR2(TIME_X,FIRST_SCORE_Y + static_cast<float>(i)*SCORE_INDEX_OFFSET));
	}

}

void HiScore::Resize(const D3DSURFACE_DESC* d) {
	// Put the dialog back in the corner (this step probably unnecessary)
	dialog.SetLocation( 0, 0 );

	// Make its size match the size of the screen
	dialog.SetSize( d->Width, d->Height );
}

bool HiScore::MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	// Pass windows messages to the resource manager and all the gui objects
	if (GameStateGUI::MsgProc(hWnd, uMsg, wParam, lParam)) return true;

	// Pressing ESC during score entry will take you to the Title or the next level:
	if (uMsg == WM_KEYDOWN && wParam == VK_ESCAPE) {

		if (Registrar::bEnteringScore)
			dxf::DXFChangeState(Registrar::kGame);
		else 
			dxf::DXFChangeState(Registrar::kTitle);

		return true;
	}

	return false;	// let the engine handle everything else.
}

void HiScore::OnGUIEvent( UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext ) {
    switch( nControlID ) {

	case IDC_EDITBOX:
		// Did they enter a high score?:
		if (nEvent == EVENT_EDITBOX_STRING) {
			// Add the score and name to the list:
			HiScoreLists[Registrar::iLastLevel].push_back( 
				HiScoreRecord( 
				  (static_cast<CDXUTEditBox*>(pControl))->GetText(), 
				  Registrar::iNumTurns, Registrar::iNumPushes, Registrar::fTime ) 
				);
			// Hide the edit box:
			(static_cast<CDXUTEditBox*>(pControl))->SetVisible(false);
			// Now we're not entering a score:
			bEnteringScore = false;
		}
		break;
	default:
		assert(false);
		break;
	}
}

HiScore::HiScoreRecord::HiScoreRecord( std::wstring name, long turns, long pushes, double time ) 
:	sName(name),
	iTurns(turns), iPushes(pushes),
	fTime(time) {
}

bool HiScore::HiScoreRecord::operator <( const HiScoreRecord &other ) const {
	// Scores are ranked based on no. of pushes, then turns then time.
	return (iPushes < other.iPushes)
		|| (iPushes == other.iPushes && iTurns < other.iTurns)
		|| (iPushes == other.iPushes && iTurns == other.iTurns && fTime < other.fTime);
}

